MACHINE LEARNING - BOOTSTRAPPING, BAGGING, BOOSTING E RANDOM FORESTS.

Autor

João Ricardo F. de Lima

Data de Publicação

16 de abril de 2024


Bootstrapping, Bagging, Boosting e Random Forests.

1 Alguns modelos preditivos costumam apresentar alta variância, outros alto viés. Nenhum destes extremos é desejável se o objetivo é maximizar a acurácia do modelo. Felizmente, ainda podemos utilizar modelos assim para tarefas preditivas, se conciliarmos com técnicas adicionais adequadas.

Aqui é mostrada as técnicas de Bootstrapping, Bagging, Boosting e Random Forests com o objetivo de aumentar o desempenho em modelos preditivos.

Bootstrapping

Bootstrapping é uma técnica estatística que permite gerar quase qualquer estatística ou estimador de interesse ao empregar a amostragem aleatória com reposição em dados observados.

A ideia desta técnica é simular múltiplos conjuntos de dados similares aos dados observados ao extrair amostras do mesmo. Cada conjunto de dados é criado ao selecionar aleatoriamente observações dos dados observados originais, com a possibilidade de que a mesma observação seja selecionada várias vezes ou nunca seja selecionada.

#setwd("~/Dropbox/tempecon/Facape")

#| fig-width: 15

# Pacotes
library(rbcb)
library(dplyr)
library(tsibble)
library(fabletools)
library(fable)
library(sidrar)
library(tidyverse)
library(forecast)
library(knitr)
library(TSstudio)
library(patchwork)
library(GetBCBData)
set.seed(1984)

# Dados
#dados <- rbcb::get_series(
#  code = c("ipca" = 433),
#  start_date = "2010-01-01",
#  end_date = "2024-01-01"
#  ) |> 
#  dplyr::mutate(date = tsibble::yearmonth(date)) |> 
#  tsibble::as_tsibble(index = date)

# Coleta e tratamento de dados
dados <- as_tibble(GetBCBData::gbcbd_get_series(
  id          = c("IPCA" = "433"),
  first.date  = "2010-01-01", 
  use.memoise = FALSE
)) |>
  dplyr::select(
    "date"     = ref.date, 
    "ipca" = value
  ) |> 
  tsibble::as_tsibble(index = date) |>
  dplyr::mutate(date = tsibble::yearmonth(date))

# No caso de se selecionar apenas alguns objetos para salvar
#save(dados, file="ipca.RData")

# Se os dados forem do próprio R, para importar é usar o ``load`` 
#load("ipca.RData")

# Modelo
modelo <- dados |>
  fabletools::model(stl = fable::AR(ipca))

# Amostras de bootstrapping
bootstrapping <- modelo |>
  fabletools::generate(
    new_data = dados, 
    times = 10, 
    bootstrap = TRUE,
    bootstrap_block_size = 8 #seleciona em blocos de 8 obs, reduz questao da autocorrelaçao da serie temporal.
    ) |>
  dplyr::select(-c(".model", "ipca"))

bootstrapping
# Previsões de bootstrap
previsao <- bootstrapping |>
  fabletools::model(ets = fable::AR(.sim)) |>
  fabletools::forecast(h = 12)

previsao
# Série original
g1 <- dados |> 
  fabletools::autoplot(.vars = ipca, size = 1) + 
  ggplot2::labs(title = "1) Série original", y = NULL, x = NULL)

# Séries simuladas de bootstrap
g2 <- bootstrapping |> 
  fabletools::autoplot(.vars = .sim) +
  fabletools::autolayer(object = dados, .vars = ipca, size = 1) + 
  ggplot2::guides(colour = "none") +
  ggplot2::labs(title = "2) Séries Bootstrap", y = NULL, x = NULL)
  
# Previsões de bootstrap
g3 <- previsao |>
  tsibble::update_tsibble(key = .rep) |>
  fabletools::autoplot(.vars = .mean) +
  fabletools::autolayer(object = dados, .vars = ipca, size = 1) + 
  ggplot2::guides(colour = "none") +
  ggplot2::labs(title = "3) Previsões Bootstrap", y = NULL, x = NULL)

# Gráfico
g1 + g2 + g3 + 
  patchwork::plot_layout(nrow = 3) +
  patchwork::plot_annotation(
    title   = "A técnica de Bootstrapping",
    caption = "Dados: IPCA/IBGE",
    theme   = ggplot2::theme(
      plot.title   = ggplot2::element_text(face = "bold", size = 22, hjust = 0.5),
      plot.caption = ggtext::element_textbox_simple()
      )
    )

As principais vantagens da técnica de bootstrapping são:

  • Simplicidade de uso e implementação;

  • Pode ser usada para obter quase qualquer estatística ou estimador;

  • É mais acurada para obter intervalos de confiança do que o uso da variância amostral ou suposições de normalidade.


Bagging

Nesse sentido, a técnica de Bootstrap aggregation ou simplesmente Bagging é um procedimento de propósito geral que possibilita justamente a redução de variância de diferentes métodos de aprendizado.

Aqui o método é introduzido no contexto de arvores de decisão, mas o mesmo pode ser utilizado em diferentes métodos de aprendizado.

Dado um conjunto de \(n\) observações independentes, \(Z_1, ..., Z_n\), cada uma com variância \(\sigma^2\), a variância da média \(\bar{Z}\) será dada por \(\frac{\sigma^2}{n}\).

Ou seja, calcular a média do conjunto de observações reduz a variância.

É imediato pensar que para reduzir a variância e, portanto, aumentar a acurácia da previsão de um determinado método estatístico de aprendizado basta pegar muitos conjuntos de treinamento da população, construir um modelo de previsão separado usando cada conjunto, definir e calcular a média das previsões resultantes.

Em outras palavras, calcula-se \(\hat{f}^{1}(x), \hat{f}^{2}(x), ..., \hat{f}^{B}(x)\) usando \(B\) conjuntos de treino, calcula-se a média deles de modo a obter um único modelo com baixa variância, dado por:

\[ \hat{f}_{\text{médio}}(x) = \frac{1}{B} \sum_{b=1}^{B} \hat{f}^{b} (x) \]

Isso não é muito prático já que, em geral, não se tem acesso a muitos conjuntos de treino. Assim, o quese pode fazer é aplicar a técnica de bootstrap, de modo a tomar diversas amostras do mesmo conjunto de treino. Assim, são gerados \(B\) diferentes conjuntos de treino. A partir daí, pode-se treinar o método no \(b\) conjunto de treino, de modo a obter \(\hat{f}^{*b} (x)\), finalmente obtendo a média das previsões

\[ \hat{f}_{\text{bag}}(x) = \frac{1}{B} \sum_{b=1}^{B} \hat{f}^{*b} (x) \]

o que é chamado de bagging.

Enquanto o método de bagging pode ser utilizado para aumentar a acurácia da previsão nos métodos de regressão, ele é particularmente útil para árvores de decisão.

Para aplicar o método à árvores de regressão, simplesmente se constrói \(B\) árvores de regressão usando \(B\) conjuntos de treino construídos através da aplicação de bagging.

Como as árvores individuais não são podadas, elas crescem bastante, tendo assim alta variância e baixo viés.

Assim, construir a média dessas árvores irá reduzir a variância.

Até aqui, o método de bagging é descrito no contexto de uma regressão, com um variável \(Y\) quantitativa. Pode-se estender o método para problemas de classificação através de diversas abordagem.

A mais simples é pensar que para um dado conjunto de teste, pode-se registrar a classe prevista por cada uma das \(B\) árvores e toma-se uma votação majoritária: a previsão que mais ocorre entre as \(B\) previsões.

Considerando o exemplo acima, do Bootstrap, o uso de bagging seria como abaixo:

# Previsões bagging
bagged <- previsao |> 
  dplyr::summarise(
    bagged_mean = mean(.mean),
    bagged_min = min(.mean),
    bagged_max = max(.mean)
    )

# Previsão bagging
g4 <- dados |> 
  fabletools::autoplot(.vars = ipca, size = 1) + 
  ggplot2::geom_ribbon(
    mapping = ggplot2::aes(ymin = bagged_min, ymax = bagged_max),
    fill    = "#282f6b",
    alpha   = 0.4,
    data    = dplyr::full_join(bagged, dados, "date")
    ) +
  fabletools::autolayer(
    object = bagged, 
    .vars  = bagged_mean, 
    col    = "#282f6b",
    size   = 1
    ) +
  ggplot2::labs(title = "4) Previsão Bagging", y = NULL, x = NULL)

# Gráfico
g1 + g2 + g3 + g4 + 
  patchwork::plot_layout(nrow = 4) +
  patchwork::plot_annotation(
    title   = "A técnica Bagging",
    caption = "Dados: IPCA/IBGE",
    theme   = ggplot2::theme(
      plot.title   = ggplot2::element_text(face = "bold", size = 22, hjust = 0.5),
      plot.caption = ggtext::element_textbox_simple()
      )
    )

Um outro exemplo detalhado e bem interessante da aplicação do método de bagging é mostrado abaixo.

set.seed(1984)

pnad <- sidrar::get_sidra(api = "/t/6381/n1/all/v/4099/p/all/d/v4099%201")

pnad$date <- seq(as.Date("2012-03-01"), as.Date("2024-02-01"), by = "1 month")

pnad_ts <- pnad |> 
  dplyr::select(
    "date"     = `date`,
    "desemprego" = `Valor`,
  ) |> 
  dplyr::mutate(date = tsibble::yearmonth(date)) |> 
  tsibble::as_tsibble(index = date)

# Série original
pnad_ts |> 
  fabletools::autoplot(.vars = desemprego, size = 1) + 
  ggplot2::labs(title = "PNAD - Desemprego", y = "% do Total", x = NULL)

# Divisão dos dados em Amostra de Treino e Amostra de Teste
split_pnad <- TSstudio::ts_split(ts.obj = pnad_ts, sample.out = 24)

dados_treino <- split_pnad$train
dados_teste <- split_pnad$test

# Modelo
modelo <- dados_treino |>
  fabletools::model(stl = fable::AR(desemprego)) 

modelo_fc <- dados_treino |>
  fabletools::model(stl = fable::AR(desemprego)) |>
  fabletools::forecast(h = 24)

forecast_original <- modelo_fc[,c(2,4)] |> 
  dplyr::select(
    "date"     = `date`,
    "previsao" = `.mean`
  ) 

# Amostras de bootstrapping
bootstrapping <- modelo |>
  fabletools::generate(
    new_data = dados_treino, 
    times = 10, 
    bootstrap = TRUE,
    bootstrap_block_size = 8 #seleciona em blocos de 8 obs, reduz questao da autocorrelaçao da serie temporal.
    ) |>
  dplyr::select(-c(".model", "desemprego"))

# Previsões de bootstrap
previsao <- bootstrapping |>
  fabletools::model(ets = fable::AR(.sim)) |>
  fabletools::forecast(h = 24)

# Previsões bagging
bagged <- previsao |>
  dplyr::summarise(
    bagged_mean = mean(.mean),
    bagged_min = min(.mean),
    bagged_max = max(.mean)
  )

forecast_bagged <- bagged |> 
  dplyr::select(
    "date"     = `date`,
    "previsao" = `bagged_mean`,
  ) 

# Série original
g1 <- pnad_ts |> 
  fabletools::autoplot(.vars = desemprego, size = 1) + 
  ggplot2::labs(title = "1) Série original", y = NULL, x = NULL)

# Séries simuladas de Modelo Original
g2 <- pnad_ts |> 
  fabletools::autoplot(.vars = desemprego, size = 1) +
  fabletools::autolayer(object = forecast_original, .vars = previsao, size = 1, colour = "red") + 
  ggplot2::labs(title = "2) Previsão Arima", y = NULL, x = NULL)

# Séries simuladas de bootstrap
g3 <- bootstrapping |> 
  fabletools::autoplot(.vars = .sim) +
  fabletools::autolayer(object = dados_treino, .vars = desemprego, size = 1) + 
  ggplot2::guides(colour = "none") +
  ggplot2::labs(title = "3) Séries Bootstrap", y = NULL, x = NULL)
  
# Previsões de bootstrap
g4 <- previsao |>
  tsibble::update_tsibble(key = .rep) |>
  fabletools::autoplot(.vars = .mean) +
  fabletools::autolayer(object = pnad_ts, .vars = desemprego, size = 1) + 
  ggplot2::guides(colour = "none") +
  ggplot2::labs(title = "4) Previsões Bootstrap", y = NULL, x = NULL)

# Previsão bagging
g5 <- pnad_ts |> 
  fabletools::autoplot(.vars = desemprego, size = 1) + 
  ggplot2::geom_ribbon(
    mapping = ggplot2::aes(ymin = bagged_min, ymax = bagged_max),
    fill    = "#282f6b",
    alpha   = 0.4,
    data    = dplyr::full_join(bagged, dados_treino, "date")
    ) +
  fabletools::autolayer(
    object = bagged, 
    .vars  = bagged_mean, 
    col    = "#282f6b",
    size   = 1
    ) +
  ggplot2::labs(title = "5) Previsão Bagging", y = NULL, x = NULL)

# Gráfico
g1 + g2 + g3 + g4 + g5 +
  patchwork::plot_layout(nrow = 6) +
  patchwork::plot_annotation(
    title   = "A técnica Bagging",
    caption = "Dados: IBGE",
    theme   = ggplot2::theme(
      plot.title   = ggplot2::element_text(face = "bold", size = 22, hjust = 0.5),
      plot.caption = ggtext::element_textbox_simple()
      )
    )

# Medidas de Acurácia
forecast_original <- ts(forecast_original, start = c(2022,3), freq = 24)
forecast_bagged <- ts(forecast_bagged, start = c(2022,3), freq = 24)
dados_teste <- ts(dados_teste, start = c(2022,3), freq = 24)

#Forecast do Arima
kable(forecast::accuracy(forecast_original, dados_teste))
ME RMSE MAE MPE MAPE ACF1 Theil’s U
Test set -1.330597 1.990205 1.330597 -16.3586 16.3586 0.9570851 1.720772
#Forecast do Bagging
kable(forecast::accuracy(forecast_bagged, dados_teste))
ME RMSE MAE MPE MAPE ACF1 Theil’s U
Test set -1.933844 2.816485 1.933844 -23.59014 23.59014 0.9627382 2.407886

Notas de rodapé

  1. Este material está baseado em diversas postagens da Análise Macro (www.analisemacro.com.br)↩︎